﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace ExchangeHog.MessageParser
{
	abstract public class BaseToken
	{
		abstract internal String Repr { get; set; }
		abstract internal int TokenTypeAsInt(); 

		abstract internal void MarkAsCRLF();
		abstract internal void MarkAsSPACE();

		abstract internal bool IsSPACE();
	}

	public class BaseLexer<Token> where Token : BaseToken, new()
	{
		private StreamReader sr;
		List<Char> lookahead;
		protected Queue<Token> Tokens { get; private set; }

		public void SetStream(Stream s) { sr = new StreamReader(s); lookahead = new List<Char>(); Tokens = new Queue<Token>(); }

		Char PopFirstFromLookaheadList() { if (lookahead.Count < 1) throw new ArgumentException(); Char ch = lookahead[0]; lookahead.RemoveAt(0); return ch; }
		Char PeekFirstFromLookaheadList() { if (lookahead.Count < 1) throw new ArgumentException(); return lookahead[0]; }

		protected delegate bool DelegateExpectCondition();
		protected void Expect(DelegateExpectCondition d, char ch, string s)
		{
			bool result = d();
			if (!result)
				throw new ApplicationException(
					String.Format(
						"Not what we expected: {0} instead of {1}", ch, s
					)
				);
		}

		Decoder decoderascii;
		byte[] sbytes;
		protected BaseLexer()
		{
			decoderascii = Encoding.ASCII.GetDecoder();
			sbytes = new byte[1];
		}

		protected Char IntToAsciiChar(int chcode)
		{
			sbytes[0] = Convert.ToByte(chcode);
			char[] tchs = new char[decoderascii.GetCharCount(sbytes, 0, 1)];
			int cBytesUsed, cCharsUsed;
			bool isCompleted;
			decoderascii.Convert(sbytes, 0, 1, tchs, 0, tchs.Length, true, out cBytesUsed, out cCharsUsed, out isCompleted);

			return tchs[0];
		}

		protected Char ReadChar() { return lookahead.Count > 0 ? PopFirstFromLookaheadList() : IntToAsciiChar(sr.Read()); }
		protected Char PeekChar() { return lookahead.Count > 0 ? PeekFirstFromLookaheadList() : IntToAsciiChar(sr.Peek()); }
		protected Char PeekChar2()
		{
			if (lookahead.Count < 1)
				lookahead.Add(ReadChar());
			if (lookahead.Count < 2)
				lookahead.Add(ReadChar());
			return Convert.ToChar(lookahead[1]);
		}
		protected Char PeekChar3()
		{
			if (lookahead.Count < 1)
				lookahead.Add(ReadChar());
			if (lookahead.Count < 2)
				lookahead.Add(ReadChar());
			if (lookahead.Count < 3)
				lookahead.Add(ReadChar());
			return Convert.ToChar(lookahead[2]);
		}
		protected void SkipChar() { if (lookahead.Count > 0) PopFirstFromLookaheadList(); else ReadChar(); }

		protected bool IsEndOfStream() { return sr.EndOfStream; }

		protected void ParseLWS()
		{
			Char ch = ReadChar();
			Expect(delegate() { return (ch == ' ' || ch == '\t'); }, ch, "space or tab");

			if (!IsEndOfStream())
			{
				bool isEndOfLWS = false;
				ch = PeekChar();
				while (!IsEndOfStream() && !isEndOfLWS)
					switch (ch)
					{
						case ' ':
						case '\t':
							SkipChar();
							ch = PeekChar();
							break;
						case '\r':
							if (PeekChar2() == '\n' && PeekChar3() == ' ' || PeekChar3() == '\t')
							{
								SkipChar(); // \r
								SkipChar(); // \n
								SkipChar(); // ' ' or '\t'
							}
							ch = PeekChar();
							break;
						default:
							isEndOfLWS = true;
							break;
					}
			}
			Token t = new Token();
			t.MarkAsSPACE();
			Tokens.Enqueue(t);
		} // end of ParseLWS

		protected void ParseCRLF()
		{
			Char ch = ReadChar();
			Expect(delegate() { return (ch == '\r'); }, ch, "CR");
			ch = ReadChar();
			Expect(delegate() { return (ch == '\n'); }, ch, "LF");

			Token t = new Token();
			t.MarkAsCRLF();
			Tokens.Enqueue(t);
		} // end of ParseCRLF

		public abstract Token NextToken();

		public Token NextNonWsToken()
		{
			Token t;
			do { t = NextToken(); } while (t.IsSPACE());
			return t;
		}

		protected Token ParseThis(object ttExpected)
		{
			Token sft = NextNonWsToken();
			if (sft.TokenTypeAsInt() != (int)ttExpected)
				throw new ApplicationException(
					String.Format(
						"Not what we expected: {0} instead of {1}", sft.ToString(), ttExpected.ToString()
					)
				);
			return sft;
		}
	}
}
